package com.smart.thrift.config.server;

import cn.hutool.core.util.StrUtil;
import com.smart.thrift.annotation.ThriftService;
import com.smart.thrift.constants.ThriftConstants;
import com.smart.thrift.model.common.ThriftServiceSignature;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import static org.springframework.context.annotation.AnnotationConfigUtils.registerAnnotationConfigProcessors;

;

@Slf4j
public class ThriftServiceBeanScanner extends ClassPathBeanDefinitionScanner {

    private final ApplicationContext applicationContext;

    public ThriftServiceBeanScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters, ApplicationContext applicationContext,
                                    Environment environment, ResourceLoader resourceLoader) {
        super(registry, useDefaultFilters);
        setEnvironment(environment);
        setResourceLoader(resourceLoader);
        registerAnnotationConfigProcessors(registry);
        this.applicationContext = applicationContext;
    }

    public ThriftServiceBeanScanner(BeanDefinitionRegistry registry, ApplicationContext applicationContext,
                                    Environment environment, ResourceLoader resourceLoader) {
        this(registry, false, applicationContext, environment, resourceLoader);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        this.addIncludeFilter(new AnnotationTypeFilter(ThriftService.class));
        Set<BeanDefinitionHolder> allDefinitionHolders = new HashSet<>();
        Set<BeanDefinitionHolder> definitionHolders = super.doScan(basePackages);

        log.info("Packages scanned by ThriftMapperBeanScanner is [{}]", StringUtils.join(basePackages, ", "));
        for (BeanDefinitionHolder definitionHolder : definitionHolders) {
            GenericBeanDefinition definition = (GenericBeanDefinition) definitionHolder.getBeanDefinition();

            log.info("Scanned and found thrift client, bean {} assigned from {}", definitionHolder.getBeanName(), definition.getBeanClassName());

            String beanClassName = definition.getBeanClassName();
            Class<?> beanClass;
            try {
                beanClass = Class.forName(beanClassName);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                continue;
            }

            ThriftService thriftService = AnnotationUtils.findAnnotation(beanClass, ThriftService.class);
            if (thriftService == null) {
                log.warn("Thrift client is not found");
                continue;
            }

            String beanName = StringUtils.isNotBlank(thriftService.value()) ? thriftService.value()
                    : (StringUtils.isNotBlank(thriftService.name()) ? thriftService.name() : StringUtils.uncapitalize(beanClass.getSimpleName()));

            // 代理service bean
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClass);
            definition.setBeanClass(GenericFactoryBean.class);
            definition.setAutowireMode(GenericBeanDefinition.AUTOWIRE_BY_TYPE);
            BeanComponentDefinition beanComponentDefinition = new BeanComponentDefinition(definitionHolder);
            allDefinitionHolders.add(beanComponentDefinition);

            boolean match = Arrays.stream(beanClass.getInterfaces()).anyMatch(clazz -> clazz.getName().endsWith("$Iface"));
            if (!match) {
                BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ThriftServerFactoryBean.class);
                beanDefinitionBuilder.addPropertyValue(ThriftConstants.Definition.BEAN_NAME, beanName);
                beanDefinitionBuilder.addPropertyValue(ThriftConstants.Definition.BEAN_CLASS, beanClass);
                beanDefinitionBuilder.addPropertyValue(ThriftConstants.Definition.BEAN_CLASS_NAME, beanClassName);
                String name = StrUtil.isNotBlank(thriftService.name()) ? thriftService.name() : thriftService.value();
                ThriftServiceSignature serviceSignature = ThriftServiceSignature.builder()
                        .serviceName(StrUtil.isBlank(name) ? beanName : name).targetServiceClass(beanClass)
                        .thriftServiceClasses(Arrays.asList(beanClass.getInterfaces())).version(thriftService.version()).build();
                beanDefinitionBuilder.addPropertyValue(ThriftConstants.Definition.SERVICE_SIGNATURE, serviceSignature);
                beanDefinitionBuilder.addPropertyValue(ThriftConstants.Definition.APPLICATION_CONTEXT, applicationContext);
                beanDefinitionBuilder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                beanDefinitionBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
                AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
                assert getRegistry() != null;
                getRegistry().registerBeanDefinition(beanName + "$Iface", beanDefinition);
            }
        }
        return allDefinitionHolders;
    }

    public static class GenericFactoryBean<T> implements FactoryBean<T> {
        private Class<T> interfaceType;

        public GenericFactoryBean(Class<T> interfaceType) {
            this.interfaceType = interfaceType;
        }

        @Override
        public T getObject() throws Exception {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(interfaceType);
            enhancer.setInterfaces(interfaceType.getInterfaces());
            enhancer.setUseCache(true);
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setUseFactory(true);
            MethodInterceptor callback = (target, method, args, methodProxy) -> methodProxy.invokeSuper(target, args);
            enhancer.setCallback(callback);
            return (T) enhancer.create();
        }

        @Override
        public Class<?> getObjectType() {
            return interfaceType;
        }
    }
}
